package container.queue;

import lombok.AllArgsConstructor;
import lombok.ToString;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * 基于数组的阻塞队列实现，在 ArrayBlockingQueue 内部，维护了一个定长数组，以便缓存队列中的数据对象，这是一个常用的阻塞队列，除了一个定
 * 长数组外，ArrayBlockingQueue 内部还保存着两个整形变量，分别标识着队列的头部和尾部在数组中的位置。
 * ArrayBlockingQueue 在生产者放入数据和消费者获取数据，都是共用同一个锁对象，由此也意味着两者无法真正并行运行，因此并发效率上相对较低，这点
 * 尤其不同于 LinkedBlockingQueue。
 * <p>
 * 通过看源码分析，其实 ArrayBlockingQueue 完全是可以使用分离锁的。但是作者 Doug Lea 并没有这么去干，理由如下：
 * * ArrayBlockingQueue 的数据写入和获取操作已经足够轻巧，以至于引入独立的锁机制，除了给代码带来额外的复杂性外，其在性能上完全占不到任何便宜。
 * <p>
 * ArrayBlockingQueue 和 LinkedBlockingQueue 间还有一个明显的不同之处在于，前者在插入或删除元素时不会产生或销毁任何额外的对象实例，而后者
 * 则会生成一个额外的 Node 对象。这在长时间内需要高效并发地处理大批量数据的系统中，其对于 GC 的影响还是存在一定的区别。
 * 而在创建 ArrayBlockingQueue 时，我们还可以控制对象的内部锁是否采用公平锁，默认采用非公平锁。
 * <p>
 * 对于 ArrayBlockingQueue，我们可以在构造的时候指定以下三个参数：
 * public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c) {
 * * 队列容量，其限制了队列中最多允许的元素个数；
 * * 指定独占锁是公平锁还是非公平锁。非公平锁的吞吐量比较高，公平锁可以保证每次都是等待最久的线程获取到锁；
 * * 可以指定用一个集合来初始化，将此集合中的元素在构造方法期间就先添加到队列中。
 */
public class T03_ArrayBlockingQueue {
    // 生产者和消费者共用这一个队列，队列容量为 10
    static ArrayBlockingQueue<Cookie> arrayBlockingQueue = new ArrayBlockingQueue<>(10);

    public static void main(String[] args) throws InterruptedException {
        // 开启一个生产者
        for (int i = 0; i < 1; i++) {
            new Produce(arrayBlockingQueue).start();
        }
        // 一个生产者，5 个消费者
        for (int i = 0; i < 5; i++) {
            new Thread(new Consume(arrayBlockingQueue)).start();
        }
    }

    @AllArgsConstructor
    static class Produce extends Thread {
        private static int i = 0;
        private final ArrayBlockingQueue<Cookie> arrayBlockingQueue;

        public void run() {
            try {
                while (i < 1000) {
                    arrayBlockingQueue.put(new Cookie("cookie" + i));
                    if (++i % 100 == 0) { // 每生产 100 个，休息 10s
                        Thread.sleep(10000);
                    }
                }
            } catch (InterruptedException e) {
                System.out.println("produce queue InterruptedException");
            }
        }
    }

    @AllArgsConstructor
    static class Consume implements Runnable {
        private final ArrayBlockingQueue<Cookie> arrayBlockingQueue;

        public void run() {
            try {
                while (true) {
                    Cookie poll = arrayBlockingQueue.poll(5, TimeUnit.SECONDS); // 如果 queue 为 null，那么 5 秒之后再去队列中取数据
                    if (poll != null)
                        System.out.println(Thread.currentThread().getName() + "--consume --" + poll);

                }
            } catch (InterruptedException e) {
                System.out.println("consume queue InterruptedException");
            }
        }
    }

    @ToString
    @AllArgsConstructor
    static class Cookie {
        private final String number;
    }
}
